package com.we.utils.iplocation;

import java.io.UnsupportedEncodingException;

/**
 * @Description:ip定位(使用byte数据方式读取)
 * @author:difeng
 * @date:2016年12月13日
 */
public class IPLocation {

    private  byte[] data;

    private  long firstIndexOffset;

    private  long totalIndexCount;

    private static final byte REDIRECT_MODE_1 = 0x01;

    private static final byte REDIRECT_MODE_2 = 0x02;

    static   final long IP_RECORD_LENGTH = 7;


    public IPLocation(byte[] data) throws Exception {
        this.data = data;
        firstIndexOffset = read4ByteAsLong(0);
        long lastIndexOffset = read4ByteAsLong(4);
        totalIndexCount = (lastIndexOffset - firstIndexOffset) / IP_RECORD_LENGTH + 1;
    }

    private long read4ByteAsLong(final int  offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8L) & 0xFF00L;
        val |= (data[offset + 2] << 16L) & 0xFF0000L;
        val |= (data[offset + 3] << 24L) & 0xFF000000L;
        return val;
    }

    private long read3ByteAsLong(final int offset) {
        long val = data[offset] & 0xFF;
        val |= (data[offset + 1] << 8) & 0xFF00;
        val |= (data[offset + 2] << 16) & 0xFF0000;
        return val;
    }

    private long search(long ip) {
        long low = 0;
        long high = totalIndexCount;
        long mid = 0;
        while(low <= high){
            mid = (low + high) >>> 1 ;
            long indexIP = read4ByteAsLong((int)(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH));
            long indexIPNext = read4ByteAsLong((int)(firstIndexOffset + mid * IP_RECORD_LENGTH));
            if(indexIP <= ip && ip < indexIPNext) {
                return read3ByteAsLong((int)(firstIndexOffset + (mid - 1) * IP_RECORD_LENGTH + 4));
            } else {
                if(ip > indexIP) {
                    low = mid + 1;
                } else if (ip < indexIP) {
                    high = mid - 1;
                }
            }
        }
        return -1;
    }

    public Location fetchIPLocation(String ip) {
        long numericIp = inet_pton(ip);
        long offset = search(numericIp);
        if(offset != -1) {
            return readIPLocation((int)offset);
        }
        return null;
    }

    private Location readIPLocation(final int offset) {
        final Location loc = new Location();
        try {
            byte redirectMode = data[offset + 4];
            if (redirectMode == REDIRECT_MODE_1) {
                long countryOffset = read3ByteAsLong((int)offset + 5);
                redirectMode = data[(int)countryOffset];
                if (redirectMode == REDIRECT_MODE_2) {
                    final QQwryString country = readString((int)read3ByteAsLong((int)countryOffset + 1));
                    loc.country = country.string;
                    countryOffset = countryOffset + 4;
                } else {
                    final QQwryString country = readString((int)countryOffset);
                    loc.country = country.string;
                    countryOffset += country.byteCountWithEnd;
                }
                loc.area = readArea((int)countryOffset);
            } else if (redirectMode == REDIRECT_MODE_2) {
                loc.country = readString((int)read3ByteAsLong((int)offset + 5)).string;
                loc.area = readArea((int)offset + 8);
            } else {
                final QQwryString country = readString((int)offset + 4);
                loc.country = country.string;
                loc.area = readArea((int)offset + 4 + country.byteCountWithEnd);
            }
            return loc;
        } catch (Exception e) {
            return null;
        }
    }

    private String readArea(final int offset) {
        byte redirectMode = data[offset];
        if (redirectMode == REDIRECT_MODE_1 || redirectMode == REDIRECT_MODE_2) {
            long areaOffset = read3ByteAsLong((int)offset + 1);
            if (areaOffset == 0) {
                return "";
            } else {
                return readString((int)areaOffset).string;
            }
        } else {
            return readString(offset).string;
        }
    }

    private QQwryString readString(int offset) {
        int pos = offset;
        final byte[] b = new byte[128];
        int i;
        for (i = 0, b[i] = data[pos++]; b[i] != 0; b[++i] = data[pos++]);
        try{
            return new QQwryString(new String(b,0,i,"GBK"),i + 1);
        } catch(UnsupportedEncodingException e) {
            return new QQwryString("",0);
        }
    }

    /**
     * @Description:“.”号分隔的字符串转换为long类型的数字
     * @param ipStr
     * @return:long
     */
    private static long inet_pton(String ipStr) {
        if(ipStr == null){
            throw new NullPointerException("ip不能为空");
        }
        String [] arr = ipStr.split("\\.");
        long ip = (Long.parseLong(arr[0])  & 0xFFL) << 24 & 0xFF000000L;
        ip |=  (Long.parseLong(arr[1])  & 0xFFL) << 16 & 0xFF0000L;
        ip |=  (Long.parseLong(arr[2])  & 0xFFL) << 8 & 0xFF00L;
        ip |=  (Long.parseLong(arr[3])  & 0xFFL);
        return ip;
    }

    private class QQwryString{

        public final String string;

        public final int byteCountWithEnd;

        public QQwryString(final String string,final int byteCountWithEnd) {
            this.string = string;
            this.byteCountWithEnd = byteCountWithEnd;
        }

        @Override
        public String toString() {
            return string;
        }

    }
}